热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

都会|两码事_Linux进程控制

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Linux-进程控制相关的知识,希望对你有一定的参考价值。Linux-进程控制

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Linux-进程控制相关的知识,希望对你有一定的参考价值。



Linux-进程控制


  • 进程创建
    • fork函数
    • fork函数返回值
    • fork常规用法
    • fork调用失败的原因

  • 写时拷贝
  • 进程终止
    • 进程退出场景
    • 进程常见退出方法
    • _exit函数&&exit函数&&return退出

  • 进程等待
    • 进程等待的方法
      • wait方法
      • waitpid方法

    • 获取子进程status
      • 退出状态
      • 终止信号

    • 进程的等待方式
      • 阻塞式等待
      • 非阻塞式等待


  • 进程程序替换
    • 替换函数
    • 函数解释
    • 命名理解
    • 总结

  • 简易的shell


进程创建

进程的创建:


  1. 命令行启动命令(程序、指令……)
  2. 通过程序自身,fork出来的子进程


fork函数



在linux中fork函数是非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。


语法:

#include
pid_t fork(void);
返回值:自进程中返回0,父进程返回子进程id,出错返回-1

进程调用fork,当控制转移到内核中的fork代码后,内核做:


  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度

注:


  • 当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方
  • fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。
  • 所有fork出来的子进程,都是以父进程为模板,数据代码继承父进程
  • 创建子进程的本质是系统多了一个进程,多了一套进程相关的数据结构(以父进程为模板)


注:


  • 上端代码中的pid是变量,是父进程创建的,是父进程运行fork中的代码的
  • pid中有两个不同的值,是因为fork() return返回的时候本质是把返回值写入变量,必定会写时拷贝(注:这里的写时拷贝并不是立刻进行写时拷贝,只是写时拷贝机制(策略),只用当父子进行数据写入或者数据修改时,才进行写时拷贝),一个变量名内容是不同的本质是父子页表映射数据到了不同的内存区域
  • pid变量里有两个不同的值,可以从而让父子进入不同的业务逻辑
  • fork函数只是创建子进程的过程,创建父子进程的依旧是操作系统

补: fork创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。


fork函数返回值


  • 子进程返回0,
  • 父进程返回的是子进程的pid

fork常规用法


  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

fork调用失败的原因


  • 系统中有太多的进程
  • 实际用户的进程数超过了限制

写时拷贝

父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本

注:


  • 写时拷贝的实现过程时,需要OS参与完成,OS时进程、内存的管理者
    总结:
  • 写时拷贝可以保证父子进程的独立性
  • 写时拷贝的意义:

    1. 并不是所有数据父子进程都会写入,不需要写入修改的数据(只读)拷贝是没有意义的,因此写时拷贝是可以避免内存和系统资源的浪费

    1. fork时,创建数据结构,如果还要将数据拷贝一份,fork函数的效率就会降低,因此写时拷贝可以提高fork的效率

    1. fork本身就是向系统索要更多的资源,如果再拷贝数据的话,索要的资源就会变多,创建子进程就很容易失败,因此写时拷贝可以提高fork创建子进程的成功率

补充:


  • Fork后子进程保留了父进程的:
  • 环境变量
  • 当前工作目录
  • 不会保留 父进程的文件锁,pending alarms和pending signals
  • 不会保留 进程号
  • fork调用通过复制父进程创建子进程,子进程与父进程运行的代码和数据完全一样
  • fork创建子进程就是在内核中通过调用clone实现

进程终止


注:


  • main函数中return 0 中的0代表程序的退出码,代表程序退出,结果是否运行正确
  • 这个退出码默认是给系统看的确认进程执行结果是否正确(0代表运行成功,!0代表运行结果有问题)
  • 用户想得知退出码,需要用到echo $? 来查看
  • echo $? 是用来查看最近一次执行的程序的退出码

进程退出场景

进程退出情况:


  1. 代码跑完,结果正确,退出码0
  2. 代码跑完,结果不正确,逻辑问题,但是没有导致程序崩溃,退出码!0
  3. 代码没有运行完毕,程序崩溃,退出码没有意义


注:


  • 退出码,可以是人为定义的,也可以是使用系统的错误码list
  • 当程序运行失败的时候,往往最关系的是失败的原因(计算机擅长处理整数类型的数据,将int转化为string(错误码描述))
  • 子进程的退出情况是父进程所关心的

总结:


  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

进程常见退出方法


  • 正常终止(可以通过 echo $? 查看进程退出码):

    1. 从main返回

    1. 调用exit

    1. _exit
  • 异常退出:
  • ctrl + c,信号终止

注:


  • 子进程运行,父进程可以关心,也可以不关心子进程的运行结果

  • 进程非正常退出:野指针、/0、越界访问……,退出码是无意义的


_exit函数&&exit函数&&return退出


注:main函数return,非main函数的return不是终止进程,而是结束进程

注:任何函数exit都表示直接终止进程



注:


  • exit:在退出的时候,会进行后续资源处理,包括刷新缓冲区
  • _exit:在退出时,不会进行后续资源储量,直接终止进程

结论:


  • 在调用exit之前:

    1. 执行用户通过 atexit或on_exit定义的清理函数。

    1. 关闭所有打开的流,所有的缓存数据均被写入

    1. 调用_exit
  • return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。
  • void _exit(int status):

    1. 参数:status 定义了进程的终止状态,父进程通过wait来获取该值

    1. 虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255。

总结:


  • 站在OS角度,进程终止的核心思想是:归还资源

    1. 释放(释放不是真的把数据结构对象销毁,而是设置为不用的状态,然后保存起来,如果不用的对象多了就有一个数据结构池(这种规则在Linux中叫Slab分派器))曾经为了管理进程所维护的所有的数据结构对象
  • 池可以提高申请空间的时间,提高用户效率

    1. 释放(不是代码和数据清空,而是把内存设置为无效就可以了)程序代码和数据占用的内存空间

    1. 取消曾经该进程的连接关系

进程等待

进程等待必要性:


  1. 回收僵尸进程,解决内存泄漏
  2. 需要获取子进程的运行结束状态(有些父进程不需要子进程的运行结束状态)
  3. 尽量父进程要晚于子进程退出,这样可以规范化进行资源回收(编码角度)

注:进程一旦变成僵尸状态用kill -9 也无能为力,因为没有办法杀死一个已经死去的进程。


进程等待的方法


wait方法

#include
#include
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

注:wait方法是等待任意一个子进程,当子进程退出时,wait就可以退出并返回该子进程的pid


循环创建进程以及循环等待:

#include
#include
#include
#include
#include
int main()

int i=0;
while(i<5)


pid_t id&#61;fork();
if(id<0)

perror("fork");
return 1;

if(id&#61;&#61;0)

int count&#61;5;
while(count)

printf("child is runing:%d,ppid:%d,pid:%d\\n",count--,getppid(),getpid());
sleep(1);

printf("child quit.....\\n");
exit(0);

i&#43;&#43;;

for(i&#61;0;i<5;i&#43;&#43;)

printf("father is waiting...\\n");
sleep(10);
pid_t ret&#61;wait(NULL);
printf("father is wait done,ret:%d\\n",ret);
sleep(3);
printf("father quit...\\n");

return 0;


注&#xff1a;一般而言&#xff0c;使用fork函数后需要让父进程进行等待


waitpid方法

#include
#include
pid_ t waitpid(pid_t pid, int *status, int options);
返回值&#xff1a;
当正常返回的时候waitpid返回收集到的子进程的进程ID&#xff1b;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0&#xff1b;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在&#xff1b;
参数&#xff1a;
pid&#xff1a;
Pid&#61;-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程返回的状态&#xff0c;则为真。&#xff08;查看进程是否是正常退出&#xff09;
WEXITSTATUS(status): 若WIFEXITED非零&#xff0c;提取子进程退出码。&#xff08;查看进程的退出码&#xff09;
options:
WNOHANG: 若pid指定的子进程没有结束&#xff0c;则waitpid()函数返回0&#xff0c;不予以等待。若正常结束&#xff0c;则返回该子进程的ID。

注&#xff1a;


  • 这里等待指定进程的的工作是由父进程来完成的
  • pid_t pid可以用fork的返回值&#xff08;父进程返回的是子进程的pid&#xff09;
  • 等待的本质是管理&#xff08;代码层面的管理&#xff09;的一种方式
  • waitpid的中的参数options可以设置成0&#xff08;阻塞式等待&#xff09;


注&#xff1a;


  • 如果子进程已经退出&#xff0c;调用wait/waitpid时&#xff0c;wait/waitpid会立即返回&#xff0c;并且释放资源&#xff0c;获得子进程退出信息。
  • 如果在任意时刻调用wait/waitpid&#xff0c;子进程存在且正常运行&#xff0c;则进程可能阻塞。
  • 如果不存在该子进程&#xff0c;则立即出错返回。

补&#xff1a;


  • waitpid默认阻塞等待任意一个或指定子进程退出&#xff0c;当options被设置为WNOHANG则函数非阻塞&#xff0c;且当没有子进程退出时&#xff0c;waitpid返回0
  • 进程等待&#xff1a;等待子进程退出&#xff08;绝对不是退出指定子进程&#xff09;&#xff0c;获取子进程返回值&#xff0c;释放子进程资源&#xff0c;避免出现僵尸进程

获取子进程status

pid_t wait(int*status)
pid_ t waitpid(pid_t pid, int *status, int options)

其中的status是输出型参数

注&#xff1a;


  • wait和waitpid&#xff0c;都有一个status参数&#xff0c;该参数是一个输出型参数&#xff0c;由操作系统填充。
  • 如果传递NULL&#xff0c;表示不关心子进程的退出状态信息。
  • 否则&#xff0c;操作系统会根据该参数&#xff0c;将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待&#xff0c;可以当作位图来看待&#xff08;这里只研究status低16比特
    位&#xff0c;status一共有32位 &#xff09;


退出状态

注&#xff1a;


  • 全局变量不可以通过设置全局变量&#xff0c;来告知父进程&#xff0c;子进程的退出码&#xff0c;因为当父子进程中的任意一个进程的数据进行写入或者修改时会进行写时拷贝&#xff08;当子进程中的数据将修改时&#xff0c;不会影响父进程的数据因此是绝对不行的&#xff09;
  • waitpid/wait拿到status的值是从OS中的PCB拿到的&#xff0c;因为子进程要退出时要将退出码交给子进程的PCB&#xff0c;waitpid/wait拿到的值是通过task_struct拿到的

终止信号



终止信号&#xff08;异常&#xff09;&#xff1a;

注&#xff1a;


  • 正常终止是没有收到任何退出信号
  • 只有正常退出退出状态才有显示&#xff0c;否则异常退出退出状态都为0&#xff08;退出状态是没有意义的&#xff09;
  • 正常退出终止信号为0&#xff1b;异常退出终止信号不为0
  • 一般进程提前终止&#xff0c;本质是该进程收到了操作系统发送的信号
  • 如果该进程运行时&#xff0c;受到kill -9 【pid】的命令时&#xff0c;该进程终止且终止信号为9

完整的进程等待&#xff1a;

或者&#xff1a;


注&#xff1a;系统提供了一堆的宏&#xff08;函数&#xff09;&#xff0c;可以用来判断退出码、退出状态


进程的等待方式


阻塞式等待



当父进程调用waitpid函数时&#xff0c;父进程等待子进程&#xff0c;直到子进程运行结束&#xff08;退出&#xff09;&#xff0c;在这期间父进程一直等&#xff08;什么事情也不做&#xff09;——阻塞式等待


pid_t ret&#61;waitpid(id,&status,0); //阻塞式等待

#include
#include
#include
#include
#include
int main()

pid_t id &#61;fork();
if(id&#61;&#61;0)

int count&#61;3;
while(count)

printf("child is running:%d,ppid:%d,pid:%d\\n",count--,getppid(),getpid());
sleep(1);

printf("child quit....\\n");
exit(0);

int status&#61;0;
pid_t ret&#61;waitpid(id,&status,0);
if(ret>0)

printf("wait success!\\n");
if(WIFEXITED(status))

printf("normal quit!\\n");
printf("quit code:%d\\n",WEXITSTATUS(status));

else

printf("process quit error!\\n");


else if(ret&#61;&#61;0)

printf("no process to wait\\n");

else

printf("wait failed\\n");
return 1;

return 0;

注&#xff1a;


  • 在C语言/C&#43;&#43;等这类编程语言中&#xff0c;所带调用的函数全部都是阻塞函数&#xff08;调用->执行&#xff08;调用方都在等待什么事情都没做&#xff09;->返回->结束&#xff09;
  • 在C语言/C&#43;&#43;等这类编程语言中&#xff0c;所带调用的函数全部都是阻塞函数&#xff0c;之所以是阻塞调用&#xff1a;执行的都是单执行流&#xff0c;并且单执行流简单
  • waitpid的中的参数options可以设置成0&#xff08;默认是阻塞式等待&#xff09;

非阻塞式等待



当父进程调用waitpid函数时&#xff0c;父进程每隔一段时间会询问子进程的状态&#xff0c;如果父进程等待失败&#xff0c;父进程会往复的等待&#xff0c;直到父进程等待子进程退出并返回该子进程的pid为止&#xff0c;在等待失败到重新询问子进程的期间里&#xff0c;父进程会做一些其他事情——非阻塞式等待


pid_t ret &#61; waitpid(id,&status,WNOHANG); //非阻塞式等待

注&#xff1a;


  • 父进程多次询问子进程的状态的方式——基于非阻塞函数的轮询检测方案
  • 上述的失败是指并不是真的失败了&#xff0c;仅仅是对方的状态没有达到预期&#xff0c;下次再检测
  • 失败分为两种&#xff1a;

    1. 并不是真的失败了&#xff0c;仅仅是对方的状态没有达到预期

    1. 真正的失败
  • 非阻塞等待的本质是检测子进程的状态&#xff08;通过多次检测&#xff0c;来检查子进程的状态&#xff09;
  • 非阻塞式等待在自然界多&#xff08;这种方式比较高效&#xff09;

补&#xff1a;


  • 计算机资源在即将被吃完的时候&#xff0c;计算机会卡住&#xff0c;服务器hang住了——宕机
  • 进程中的“等” &#xff1a; 将当前进程放入到等待队列&#xff08;进程“等”时是不可能将进程放在CPU中的&#xff09;&#xff0c;并将进程状态设置为非R状态&#xff0c;当满足唤醒条件时&#xff0c;唤醒进程将进程从等待队列放回到运行队列&#xff0c;并将进程的状态设置为R状态&#xff08;这些操作都是OS负责完成的&#xff09;
  • wait、waitpid是系统函数&#xff0c;它们都是通过操作系统来调的&#xff0c;因此当进程等待或进程调度时都是由OS完成的
  • 进程PCB&#xff08;task_struct&#xff09;中存有退出码、退出信号&#xff08;wait、waitpid系统函数&#xff0c;它们是通过PCB从而得到子进程的status的&#xff09;……

进程程序替换

创建子进程的目的&#xff1a;


  1. 执行父进程的部分代码
  2. 执行其他程序的代码&#xff08;需要进行程序替换&#xff09;

进程程序替换——当父进程创建出子进程让子进程执行其他程序的代码&#xff0c;这时该程序会从磁盘中将代码和数据直接加载到物理内存中&#xff0c;将子进程的页表由指向父进程的代码和数据指向到该程序的代码和数据

注&#xff1a;


  • 程序&#xff08;代码和数据的集合&#xff09;本质是文件
  • 操作系统是通过写时拷贝机制的方式将程序代码和数据直接加载到物理空间上交给子进程
  • 在进程程序替换的时候&#xff0c;没有创建新的子进程&#xff0c;原因是该进程相关的内核数据结构没受影响&#xff08;该进程的pid没变&#xff09;
  • 进程的程序替换是不改变进程内核的数据结构的&#xff0c;而只会修改部分的页表和数据&#xff0c;将新程序的代码和数据加载到内存&#xff0c;重新构建映射关系&#xff0c;和父进程彻底脱离

替换原理&#xff1a;
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变


替换函数

int execl(const char *path, const char *arg, ...);

注&#xff1a;


  • 进程的程序替换是通过exec*函数来完成替换的
  • exec*函数&#xff0c;不用考虑返回值&#xff0c;只要返回一定是这个函数调用失败了

补&#xff1a;


  • 当软件运行时&#xff0c;一定要先将软件加载到内存中&#xff0c;软件从磁盘中加载到内存一定是通过exec*特殊函数&#xff08;加载器&#xff09;来完成的

int execlp(const char *file, const char *arg, ...);


注&#xff1a;一般环境变量PATH&#xff0c;都是一些系统命令才能在PATH中找到&#xff0c;或者还可以把自己的命令导入到PATH中

Linux下的环境变量与命令行参数

int execv(const char *path, char *const argv[]);


int execvp(const char *file, char *const argv[]);

int execle(const char *path, const char *arg, ..., char * const envp[]);

int execve(const char *filename, char *const argv[],char *const envp[]);

补&#xff1a;


  • 上述代码中的myenv自定义的环境变量&#xff08;相当于是局部环境变量&#xff09;&#xff0c;不能被mycmd可执行程序继承&#xff08;Linux下的继承与编程语言下的继承是两码事&#xff09;&#xff0c;因此想要在在mycmd中打印出myenv就必须使用execle、execve、execvpe
  • 用Makefile编译多个源文件&#xff08;过程如下&#xff09;&#xff1a;

int execvpe(const char *file, char *const argv[], char *const envp[]);


注&#xff1a;


  • 使用execvpe是需要注意自己编译的可执行程序是无法在系统默认路径下找到的需要将可执行程序所在路径放到默认路径下即可
  • execve是其余六个函数的底层实现&#xff0c;只不过是改变了其余函数的调用方式&#xff08;参数&#xff09;

函数解释


  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
  • 如果调用出错则返回-1
  • 所以exec函数只有出错的返回值而没有成功的返回值。

命名理解

这六个exec系列函数的函数名都以exec开头&#xff0c;其后缀的含义如下&#xff1a;


  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : 有p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量


事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节。换句话说execve是其余六个函数的底层实现&#xff0c;只不过是改变了其余函数的调用方式&#xff08;参数&#xff09;

exec*函数之间的关系&#xff1a;


总结


  • 什么是程序替换&#xff1a;通过exec*让特定进城去加载磁盘中的其它进程&#xff0c;以达到运行的目的&#xff0c;期间不创建新的进程
  • 为什么要程序替换&#xff1a;

    1. 子进程执行父进程部分代码

    1. 子进程自身执行新的程序的需求
  • 如何程序替换&#xff1a;通过exec*进行程序替换
  • exec*只要返回就说明出错了

补充&#xff1a;


  • 程序替换是在当前进程pcb并不退出的情况下&#xff0c;替换当前进程正在运行的程序为新的程序&#xff08;加载另一个程序在内存中&#xff0c;更新页表信息&#xff0c;初始化虚拟地址空间&#xff09;

简易的shell

用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表&#xff0c;它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程&#xff0c;然后在那个进程中运行ls程序并等待那个进程结束。然后shell读取新的一行输入&#xff0c;建立一个新的进程&#xff0c;在这个进程中运行程序 并等待这个进程结束。




写一个shell&#xff0c;需要循环以下过程:


  1. 获取命令行
  2. 解析命令行
  3. 建立一个子进程&#xff08;fork&#xff09;
  4. 替换子进程&#xff08;execvp&#xff09;
  5. 父进程等待子进程退出&#xff08;waitpid&#xff09;

注&#xff1a;


  • 解析命令需要分为内置命令&#xff08;chdir&#xff09;和非内置命令&#xff08;fork及execvp&#xff09;
  • 内置命令不需要创建子进程&#xff0c;而是在shell内的一个函数调用来使用的

代码如下&#xff1a;

#include
#include
#include
#include
#include
#include
#define NUM 128
#define SIZE 32
char command_line[NUM];
char *command_parse[SIZE];
int main()

while(1)

memset(command_line,&#39;\\0&#39;,sizeof(command_line));
printf("[lc&#64;myhost my_shell]$ ");
fflush(stdout);
//数据读取
if(fgets(command_line,NUM-1,stdin))

command_line[strlen(command_line)-1]&#61;&#39;\\0&#39;;
//字符串&#xff08;命令行数据分析&#xff09;
int index&#61;0;
command_parse[index]&#61;strtok(command_line," ");
while(1)

index&#43;&#43;;
command_parse[index]&#61;strtok(NULL," ");
if(command_parse[index]&#61;&#61;NULL)

break;
var cpro_id = "u6885494";

推荐阅读
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 基于事件驱动的并发编程及其消息通信机制的同步与异步、阻塞与非阻塞、IO模型的分类
    本文介绍了基于事件驱动的并发编程中的消息通信机制,包括同步和异步的概念及其区别,阻塞和非阻塞的状态,以及IO模型的分类。同步阻塞IO、同步非阻塞IO、异步阻塞IO和异步非阻塞IO等不同的IO模型被详细解释。这些概念和模型对于理解并发编程中的消息通信和IO操作具有重要意义。 ... [详细]
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
author-avatar
ni是我的另一半
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有